/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: ff_container.c
 *
 * Purpose: wrapper ffmpeg as avp container for av player.
 *
 * Developer:
 *   wen.gu , 2022-01-18
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "ff_container.h"
#include "avp_container.h"
#include "avp_error.h"

#include "avp_log.h"

#include "ff_fmt_convert.h"

/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define TIME_CORRECTION(time_org, time_base) ((time_org) * 1000 * (time_base).num / (time_base).den)

/** a sepcial value to make sure No confusion */
#define STREAM_INVALID_IDX (-33)
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
typedef struct _ff_container
{
    AVFormatContext *ctx;
    GS32 playing_stream_index[AVMEDIA_TYPE_NB];
} ff_container_t;

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/


static GU32 ff_scan_stream_count(AVFormatContext* ctx, enum AVMediaType type)
{
    if (type == AVMEDIA_TYPE_UNKNOWN)
    {
        return 0;
    }

    GU32 count = 0;
    AVStream** st = ctx->streams;
    for (size_t i = 0; i < ctx->nb_streams; i++)
    {
        if (st[i]->codecpar->codec_type == type)
        {
            count++;
        }
    }

    return count;    
}

static inline void playing_stream_index_init(GS32 playing_indexes[], GU32 count)
{
    for (size_t i = 0; i < count; i++)
    {
        playing_indexes[i] = STREAM_INVALID_IDX;
    }    
}

static inline GBOL is_selected_stream(GS32 playing_indexes[], AVFormatContext* ctx, GU32 stream_index)
{
    return playing_indexes[ctx->streams[stream_index]->codecpar->codec_type] != STREAM_INVALID_IDX;
}
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

GErrc ff_container_initialize(GPHD* handle, const GSTR url)
{
    ff_container_t* container = (ff_container_t*)malloc(sizeof(ff_container_t));

    if (!container)
    {
        LOGE("create ff container instance failed\n");
        return G_ErrInsufficientResources;
    }

    int ret = avformat_open_input(&container->ctx, url, NULL, NULL);

    if (ret != 0)
    {
        LOGE("open ff format failed(%d)\n", AVERROR(ret));
        free(container);
        return G_ErrInsufficientResources;
    }

    playing_stream_index_init(container->playing_stream_index, AVP_MEDIA_CATERORY_MAX);

    *handle = (GPHD)container;
    return G_OK;
}

void ff_container_finish(GPHD handle)
{
    if (handle)
    {
        ff_container_t* container = (ff_container_t*)handle;
        avformat_close_input(&container->ctx);
    }
}

GErrc ff_container_stream_get_info(GPHD handle, GU32 index, avp_media_stream_t* stream_info)
{

    if (!handle || !stream_info)
    {
        return G_ErrBadParameter;
    }
    //ff_container_t* container = ;
    AVFormatContext* ctx = ((ff_container_t*)handle)->ctx;

    if (index >= ctx->nb_streams)
    {
        return G_ErrOutOfRange;
    }

    AVStream* st = ctx->streams[index];
    stream_info->index = st->index;
    stream_info->id = st->id;
    stream_info->start_time = (st->start_time == AV_NOPTS_VALUE) ? AVP_NO_PTS : TIME_CORRECTION(st->start_time, st->time_base);
    stream_info->duration = (st->duration <= 0) ? 0 : TIME_CORRECTION(st->duration, st->time_base);    
    stream_info->pkt_queue = NULL;
    avp_codec_info_t* codec_info = &stream_info->codec_info;
    AVCodecParameters* codecpar = st->codecpar;

    codec_info->category = ff_category_to_avp(codecpar->codec_type);
    codec_info->codec_id = ff_codec_id_to_avp(codecpar->codec_id);
    codec_info->codec_tag = codecpar->codec_tag;
    codec_info->width_or_channels = (codec_info->category == AVP_MEDIA_CATEGORY_AUDIO) ? codecpar->channels : codecpar->width;
    codec_info->height_or_bits_per_sample = (codec_info->category == AVP_MEDIA_CATEGORY_AUDIO) ? codecpar->bits_per_coded_sample : codecpar->height;
    codec_info->frame_or_sample_rate = (codec_info->category == AVP_MEDIA_CATEGORY_AUDIO) ? codecpar->sample_rate : (st->avg_frame_rate.den ? (st->avg_frame_rate.num * 1000 / st->avg_frame_rate.den) : 0);
    codec_info->channel_layout = codecpar->channel_layout;
    codec_info->bit_rate = codecpar->bit_rate;

    if (codecpar->extradata && (codecpar->extradata_size > 0))
    {
        codec_info->extra_data = (GBUF)malloc(codecpar->extradata_size);
        if (codec_info->extra_data)
        {
            memcpy(codec_info->extra_data, codecpar->extradata, codecpar->extradata_size);
            codec_info->extra_data_size = codecpar->extradata_size;
        }
        else
        {//todo refine me??
            LOGE("alloc extra data buffer with size(%d) failed\n", codecpar->extradata_size);
        }
    }

    return G_OK;

}

GErrc ff_container_stream_select(GPHD handle,  GU32 index) /** the index is in all streams, not for a special category */
{
    if (!handle)
    {
        return G_ErrBadParameter;
    } 

    ff_container_t* container = (ff_container_t*)handle;
    AVFormatContext* ctx = container->ctx; 

    if (index >= ctx->nb_streams)
    {
        return G_ErrOutOfRange;
    } 

    container->playing_stream_index[ctx->streams[index]->codecpar->codec_type] = index;

    return G_OK;

}

GErrc ff_container_stream_unselect(GPHD handle, GU32 index)
{
    if (!handle)
    {
        return G_ErrBadParameter;
    } 

    ff_container_t* container = (ff_container_t*)handle;
    AVFormatContext* ctx = container->ctx; 

    if (index >= ctx->nb_streams)
    {
        return G_ErrOutOfRange;
    } 

    container->playing_stream_index[ctx->streams[index]->codecpar->codec_type] = STREAM_INVALID_IDX;

    return G_OK;
}


avp_media_category_t ff_container_stream_category(GPHD handle, GU32 index)
{
    if (handle)
    {
        AVFormatContext* ctx = ((ff_container_t*)handle)->ctx;  
        if (index < ctx->nb_streams)
        {
            return ff_category_to_avp(ctx->streams[index]->codecpar->codec_type);
        }
    }

    return AVP_MEDIA_CATEGORY_UNKNOWN;
}




GU32 ff_container_stream_count(GPHD handle)
{
    if (handle)
    {
        return ((ff_container_t*)handle)->ctx->nb_streams;  
    }

    return 0;
}

GU32 ff_container_stream_count_ex(GPHD handle, avp_media_category_t category)
{
    if (handle && (category != AVP_MEDIA_CATEGORY_UNKNOWN))
    {
        return ff_scan_stream_count(((ff_container_t*)handle)->ctx, avp_category_to_ff(category));
    }

    return 0;
}

GErrc ff_container_read(GPHD handle, avp_packet_t** pkt)
{
    if (!handle || !pkt)
    {
        return G_ErrBadParameter;
    } 
    
    GS32 * playing_stream_index = ((ff_container_t*)handle)->playing_stream_index;
    AVFormatContext* ctx = ((ff_container_t*)handle)->ctx;  
    int res = 0;
    AVPacket av_pkt = { 0 };

    av_init_packet(&av_pkt);
    
    do
    {
        av_packet_unref(&av_pkt);
        res = av_read_frame(ctx, &av_pkt);

    } while ((res == 0) && (!is_selected_stream(playing_stream_index, ctx, av_pkt.stream_index)));

    if (res == 0)
    {
        avp_packet_t* ap = avp_packet_create(av_pkt.size);

        if (ap)
        {
            AVRational* time_base = &ctx->streams[av_pkt.stream_index]->time_base;
            memcpy(ap->buf + ap->offset, av_pkt.data, av_pkt.size);
            ap->stream_idx = av_pkt.stream_index;
            ap->dts = TIME_CORRECTION(av_pkt.dts, *time_base);
            ap->pts = TIME_CORRECTION(av_pkt.pts, *time_base);
            ap->duration = TIME_CORRECTION(av_pkt.duration, *time_base);
            ap->size = av_pkt.size;
            *pkt = ap;
            return G_OK;
        }
        else
        {
            LOGE("alloc avp packet instance with size(%d) failed\n", av_pkt.size);
            av_packet_unref(&av_pkt);
            return G_ErrInsufficientResources;
        }
    }

    return G_ErrUndefined; /** todo refine me , proccess eof?? */
}

///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////


static const avp_container_ops g_ff_container_ops = 
{
    .initialize      = ff_container_initialize,
    .finish          = ff_container_finish,
    .stream_get_info = ff_container_stream_get_info,
    .stream_select   = ff_container_stream_select,
    .stream_unselect = ff_container_stream_unselect,
    .stream_count    = ff_container_stream_count,
    .stream_count_ex = ff_container_stream_count_ex,
    .stream_category = ff_container_stream_category,
    .read            = ff_container_read,
};


void ff_container_register(void)
{
    avp_container_register(&g_ff_container_ops);
}